package jia.game;

import javax.swing.border.LineBorder;
import jia.core.Compass;
import jia.core.Module;
import jia.core.Space;
import jia.core.Wall;
import jia.game.GenericFlags;
import jia.core.GameStateManager;
import jia.core.flags.WallFlags;
import org.apache.log4j.Logger;

import javax.vecmath.Vector2d;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.AffineTransform;

// ====================================================
/**
 * @author Alec Lanter
 *         <p/>
 *         TODO: Still needs some way of ensuring that one-way walls display "properly".
 */
public class AreaViewport extends AbstractViewport {
    private static final Logger log = Logger.getLogger(AreaViewport.class);
    protected int gridWidth = 32;
    protected int gridHeight = 32;
    protected static final Color COLOR_BROWN = new Color(191, 128, 16);
    
    protected int viewportTop, viewportLeft;
    protected int lastMouseDragX, lastMouseDragY;
    
    // ====================================================
    /**
     */
    public AreaViewport() {
        setSize(new Dimension(176, 209));
        setOpaque(true);
        setBorder(new LineBorder(Color.WHITE, 2));
        viewportTop = 0;
        viewportLeft = 0;
        addMouseListener(new MyMouseListener());
        addMouseMotionListener(new MyMouseMotionListener());
        setCursor(new Cursor(Cursor.HAND_CURSOR));
        borderInsets = new Insets(0, 0, 0, 0);
        if (GenericFlags.debugMessages) {
            log.debug("Created new instance");
        }
    }
    
    // ====================================================
    /**
     *
     */
    protected void renderView(Graphics2D g) {
        if (gameState == null) return;
        
        // Don't draw outside of our borders
        g.clipRect(borderInsets.left, borderInsets.top, getWidth() - borderInsets.right, getHeight() - borderInsets.bottom);
        
        AffineTransform originalTransform = g.getTransform();
        
        g.transform(AffineTransform.getTranslateInstance(viewportLeft, viewportTop));
        
        Module module = gameState.getPlayerState().getCurrentModule();
        int modWidth = module.getWidth();
        int modHeight = module.getHeight();
        
        // Clear the screen
        g.setColor(ComponentUtils.getGlobalBackgroundColor());
        g.fillRect(0, 0, modWidth * gridWidth, modHeight * gridHeight);
        
        // Render out the area, one Space at a time
        int gridxPos, gridyPos;
        Space currentSpace;
        for (int x = 0; x < modWidth; x++) {
            gridxPos = gridWidth * x;
            for (int y = 0; y < modHeight; y++) {
                gridyPos = gridHeight * y;
                currentSpace = module.getSpace(x, y);
                
                if (currentSpace != null) {
                    // Render each wall
                    drawWall(currentSpace.getWall(Compass.NORTH), gridxPos, gridyPos, g);
                    drawWall(currentSpace.getWall(Compass.SOUTH), gridxPos, gridyPos, g);
                    drawWall(currentSpace.getWall(Compass.EAST), gridxPos, gridyPos, g);
                    drawWall(currentSpace.getWall(Compass.WEST), gridxPos, gridyPos, g);
                }
            }
        }
        
        // Draw an arrow at our location
        Vector2d playerPos = gameState.getPlayerState().getPosition();
        Compass playerFacing = gameState.getPlayerState().getFacing();
        
        if (playerPos != null) {
            int px = (int) playerPos.x * gridWidth + (gridWidth / 2);
            int py = (int) playerPos.y * gridHeight + (gridHeight / 2);
            g.setColor(Color.GREEN);
            switch (playerFacing.getDirectionalValue()) {
                case Compass.NORTH_VAL:
                    g.drawLine(px, py, px, py - 6);
                    g.drawLine(px, py - 6, px - 3, py - 3);
                    g.drawLine(px, py - 6, px + 3, py - 3);
                    break;
                case Compass.SOUTH_VAL:
                    g.drawLine(px, py, px, py + 6);
                    g.drawLine(px, py + 6, px - 3, py + 3);
                    g.drawLine(px, py + 6, px + 3, py + 3);
                    break;
                case Compass.EAST_VAL:
                    g.drawLine(px, py, px + 6, py);
                    g.drawLine(px + 6, py, px + 3, py - 3);
                    g.drawLine(px + 6, py, px + 3, py + 3);
                    break;
                case Compass.WEST_VAL:
                    g.drawLine(px, py, px - 6, py);
                    g.drawLine(px, py, px - 6, py);
                    g.drawLine(px - 6, py, px - 3, py - 3);
                    g.drawLine(px - 6, py, px - 3, py + 3);
                    
            }
        }
        
        // Put our graphics object back in pristine shape, in case our parent
        // and/or child class wants to do some rendering
        g.setTransform(originalTransform);
        g.setClip(null);
    }
    
    // Render a line for a wall at the given graphics location
    private void drawWall(Wall wall, int x, int y, Graphics2D g) {
        if (wall == null) return;
        
        Compass facing = wall.getFacing();
        int x2 = x;
        int y2 = y;
        
        switch (facing.getDirectionalValue()) {
            case Compass.SOUTH_VAL:
                y += gridHeight - 1;
                y2 += gridHeight - 1;
                x2 += gridWidth - 1;
                break;
            case Compass.NORTH_VAL:
                x2 += gridWidth - 1;
                break;
            case Compass.EAST_VAL:
                x += gridWidth - 1;
                x2 += gridWidth - 1;
                y2 += gridHeight - 1;
                break;
            case Compass.WEST_VAL:
                y2 += gridHeight - 1;
                break;
        }
        
        g.setColor(COLOR_BROWN);
        g.drawLine(x, y, x2, y2);
        
        // Draw a break in the line, if the wall is open
        if (wall.getFlags().hasFlag(WallFlags.OPEN)) {
            g.setColor(ComponentUtils.getGlobalBackgroundColor());
            switch (facing.getDirectionalValue()) {
                case Compass.SOUTH_VAL:
                case Compass.NORTH_VAL:
                    x += (gridWidth / 2) - (gridWidth / 4);
                    x2 -= (gridWidth / 2) - (gridWidth / 4);
                    break;
                case Compass.EAST_VAL:
                case Compass.WEST_VAL:
                    y += (gridHeight / 2) - (gridHeight / 4);
                    y2 -= (gridHeight / 2) - (gridHeight / 4);
                    break;
            }
            g.drawLine(x, y, x2, y2);
        }
    }
    
    // ====================================================
    /**
     *
     */
    public void updateGameState(GameStateManager gameState) {
        if (this.gameState == null) {
            int modWidth = gameState.getPlayerState().getCurrentModule().getWidth();
            int modHeight = gameState.getPlayerState().getCurrentModule().getHeight();
            
            setPreferredSize(new Dimension(modWidth * gridWidth, modHeight * gridHeight));
        }
        
        super.updateGameState(gameState);
        centerViewport();
    }
    
    // ====================================================
    /**
     */
    protected void centerViewport() {
        if (gameState == null) return;
        
        Vector2d pos = gameState.getPlayerState().getPosition();
        int x = (int) pos.x;
        int y = (int) pos.y;
        int numSquaresHigh = getSize().height / gridHeight;
        int numSquaresWide = getSize().width / gridWidth;
        
        x -= numSquaresWide / 2;
        y -= numSquaresHigh / 2;
        
        if (x < 0) x = 0;
        
        if (y < 0) y = 0;
        
        viewportLeft = -x * gridWidth;
        viewportTop = -y * gridHeight;
        repaint();
    }
    
    // ====================================================
    /**
     * @param dx
     * @param dy
     */
    protected void scrollView(int dx, int dy) {
        int maxY = gameState.getPlayerState().getCurrentModule().getHeight() * gridHeight;
        int maxX = gameState.getPlayerState().getCurrentModule().getWidth() * gridWidth;
        
        viewportLeft += dx;
        viewportTop += dy;
        
        if (viewportLeft > 0) viewportLeft = 0;
        if (viewportLeft < -maxX) viewportLeft = -maxX;
        
        if (viewportTop > 0) viewportTop = 0;
        if (viewportTop < -maxY) viewportTop = -maxY;
        
        repaint();
    }
    
    // ====================================================
    /**
     * @author Alec Lanter
     *         <p/>
     *         Used to detect dragging in the component for movement
     */
    private class MyMouseMotionListener
        extends MouseMotionAdapter {
        
        public void mouseDragged(MouseEvent evt) {
            int dx = evt.getX() - lastMouseDragX;
            int dy = evt.getY() - lastMouseDragY;
            scrollView(dx, dy);
            lastMouseDragX = evt.getX();
            lastMouseDragY = evt.getY();
        }
    }
    
    // ====================================================
    /**
     * Used to monitor the viewport scroll area
     *
     * @author Alec Lanter
     */
    private class MyMouseListener
        extends MouseAdapter {
        // ====================================================
        /**
         * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
         */
        public void mousePressed(MouseEvent evt) {
            lastMouseDragX = evt.getX();
            lastMouseDragY = evt.getY();
        }
        
        // ====================================================
        /**
         * @see java.awt.event.MouseAdapter#mouseClicked(java.awt.event.MouseEvent)
         */
        public void mouseClicked(MouseEvent evt) {
            if (evt.getClickCount() == 2) {
                centerViewport();
            }
        }
        
    }
}
